package dao

import (
	"database/sql"
	gamedb "game/game-db"
	isql "game/game-db/mysql/sql"
	"game/game-db/orm"
	"game/game-db/orm/meta"
	"game/game-db/orm/metag"
	"time"
)

type SplitBaseDao[T orm.IDbEntity] struct {
	OriginDao[T]

	tableNameToSql map[string]isql.SplitSql
	splitMeta      *meta.SplitEntityMeta
}

func NewSplitBaseDao[T orm.ISplitDbEntity](database *sql.DB, entityTemplate T) gamedb.IDao[T] {
	entityMeta := meta.NewSplitEntityMeta(entityTemplate)
	var dao SplitBaseDao[T]
	dao.database = database
	dao.entityMeta = entityMeta
	dao.splitMeta = entityMeta
	dao.tableNameToSql = make(map[string]isql.SplitSql)

	if entityMeta.SplitWay() != metag.VALUE {
		tableName := entityMeta.GetSplitTableName(time.Now().UnixMilli())
		isql := isql.NewSplitSql(entityMeta, tableName)
		dao.tableNameToSql[isql.TableName()] = isql
		//TODO 自动建表
		BuildTable(entityMeta, database, tableName)
	}
	return &dao
}

func (t *SplitBaseDao[T]) Sql(entity T) isql.ISql {
	return nil
}

func (t *SplitBaseDao[T]) getSql(tableName string) isql.ISql {
	sql, ok := t.tableNameToSql[tableName]
	if ok {
		return &sql
	}
	isql := isql.NewSplitSql(t.splitMeta, tableName)
	t.tableNameToSql[isql.TableName()] = isql
	BuildTable(t.entityMeta, t.database, tableName)
	return &isql
}

func (t *SplitBaseDao[T]) Insert(entity T) error {
	tableName := t.splitMeta.GetSplitTableNameByEntity(entity)
	sql := t.getSql(tableName)
	if t.entityMeta.PrimaryKeyStrategy() == metag.AUTOWIRE {
		return insertWireAutoId(t.entityMeta, t.database, entity, sql.InsertSql())
	} else {
		return insert(t.entityMeta, t.database, entity, sql.InsertSql())
	}
}

func (t *SplitBaseDao[T]) InsertAll(entities []T) {
	entityMap := t.splitToMap(entities)
	if t.entityMeta.PrimaryKeyStrategy() == metag.AUTOWIRE {
		for tableName, value := range entityMap {
			sql := t.getSql(tableName)
			insertAllWireAutoId(t.entityMeta, t.database, value, sql.InsertSql())
		}
	} else {
		for tableName, value := range entityMap {
			sql := t.getSql(tableName)
			insertAll(t.entityMeta, t.database, value, sql.InsertHeadSql(), sql.InsertBodySql())
		}
	}
}

func (t *SplitBaseDao[T]) Update(entity T) {
	tableName := t.splitMeta.GetSplitTableNameByEntity(entity)
	sql := t.getSql(tableName)
	update(t.entityMeta, t.database, entity, sql.UpdateSql())
}

func (t *SplitBaseDao[T]) UpdateAll(entities []T) {
	entityMap := t.splitToMap(entities)
	for tableName, value := range entityMap {
		sql := t.getSql(tableName)
		updateAll(t.entityMeta, t.database, value, sql.UpdateSql())
	}
}

func (t *SplitBaseDao[T]) Delete(entity T) {
	tableName := t.splitMeta.GetSplitTableNameByEntity(entity)
	sql := t.getSql(tableName)
	delete(t.entityMeta, t.database, entity, sql.DeleteSql())
}

func (t *SplitBaseDao[T]) DeleteAll(entities []T) {
	entityMap := t.splitToMap(entities)
	for tableName, value := range entityMap {
		sql := t.getSql(tableName)
		deleteAll(t.entityMeta, t.database, value, sql.DeleteSql())
	}
}

func (t *SplitBaseDao[T]) splitToMap(entities []T) map[string][]T {
	entityMap := make(map[string][]T)
	for _, entity := range entities {
		tableName := t.splitMeta.GetSplitTableNameByEntity(entity)
		splitSlice, ok := entityMap[tableName]
		if !ok {
			splitSlice = make([]T, 0)
		}
		splitSlice = append(splitSlice, entity)
		entityMap[tableName] = splitSlice
	}
	return entityMap
}

func (t *SplitBaseDao[T]) TableName() string {
	return t.entityMeta.TableName()
}

func (t *SplitBaseDao[T]) GetByPrimaryKey(key interface{}) (T, bool) {
	panic("split table unsupport this operation GetByPrimaryKey")
}

func (t *SplitBaseDao[T]) QueryWhere(where string, args ...interface{}) []T {
	panic("split table unsupport this operation GetWhere")
}

func (t *SplitBaseDao[T]) All() []T {
	return nil
}
